package com.lap.gateway.filter;

import com.lap.framework.constant.Header;
import com.lap.framework.crypto.EncodeUtil;
import com.lap.framework.dto.result.Result;
import com.lap.framework.enums.SystemCode;
import com.lap.gateway.entity.Security;
import com.lap.gateway.entity.acl.User;
import com.lap.gateway.service.AuthService;
import com.lap.gateway.tools.Constant;
import com.lap.gateway.tools.GatewayError;
import com.lap.gateway.tools.MonoHolder;
import jakarta.annotation.Resource;
import java.util.Objects;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 鉴权过滤器，用户端/第三方系统TOKEN验证（后面所有的过滤器应该比该过滤器低）。
 *
 * <ol>
 *   <li>链路从RequestId开始
 *   <li>鉴权过滤器
 *   <li>调用权限服务Token验证
 *   <li>授权过滤器
 *   <li>调用权限服务获取权限资源
 * </ol>
 *
 * @author Shuisheng Lao(劳水生)
 * @version 0.0.1
 */
@Slf4j
@Order(Constant.DEFAULT_ORDER)
@Component
public class AuthenticationFilter implements GlobalFilter {
  private static final String AUTHORIZATION = "Authorization";
  public static final String BEARER = "Bearer ";

  @Resource private AuthService authService;
  @Resource private Security security;

  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    if (security.ignoreToken(request.getURI().getPath(), request.getMethod().name())) {
      return chain.filter(exchange);
    }

    Optional<String> optional = Optional.ofNullable(resolverToken(exchange.getRequest()));
    return optional
        .map(token -> getVoidMono(exchange, chain, token))
        .orElseGet(
            () -> {
              Result<?> result = Result.fail(GatewayError.UNAUTHORIZED);
              return MonoHolder.onError(exchange.getResponse(), result);
            });
  }

  private Mono<Void> getVoidMono(
      ServerWebExchange exchange, GatewayFilterChain chain, String token) {
    return authService
        .getUserByToken(token)
        .flatMap(
            result -> {
              if (SystemCode.failure(result.getCode()) || Objects.isNull(result.getData())) {
                return MonoHolder.onError(exchange.getResponse(), result);
              }

              ServerHttpRequest newRequest = newRequest(exchange.getRequest(), result.getData());
              return chain.filter(exchange.mutate().request(newRequest).build());
            });
  }

  private String resolverToken(ServerHttpRequest request) {
    String token =
        Optional.of(request.getHeaders().getFirst(AUTHORIZATION))
            .orElse(request.getQueryParams().getFirst(AUTHORIZATION));

    return Optional.of(token)
        .filter(e -> e.contains(BEARER))
        .map(e -> e.substring(BEARER.length()))
        .orElse(token);
  }

  private ServerHttpRequest newRequest(ServerHttpRequest request, User user) {
    return request
        .mutate()
        .headers(
            header -> {
              header.add(Header.USER_ID, String.valueOf(user.getUserId()));
              header.add(Header.USER_NAME, user.getUsername());
              header.add(Header.FULL_NAME, EncodeUtil.encode(user.getFullName()));
            })
        .build();
  }
}
